{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "3c93ac5b",
   "metadata": {},
   "source": [
    "# Running Native Functions"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "40201641",
   "metadata": {},
   "source": [
    "Two of the previous notebooks showed how to [execute semantic functions inline](./03-semantic-function-inline.ipynb) and how to [run prompts from a file](./02-running-prompts-from-file.ipynb).\n",
    "\n",
    "In this notebook, we'll show how to use native functions from a file. We will also show how to call semantic functions from native functions.\n",
    "\n",
    "This can be useful in a few scenarios:\n",
    "\n",
    "* Writing logic around how to run a prompt that changes the prompt's outcome.\n",
    "* Using external data sources to gather data to concatenate into your prompt.\n",
    "* Validating user input data prior to sending it to the LLM prompt.\n",
    "\n",
    "Native functions are defined using standard Python code. The structure is simple, but not well documented at this point.\n",
    "\n",
    "The following examples are intended to help guide new users towards successful native & semantic function use with the SK Python framework."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "d90b0c13",
   "metadata": {},
   "source": [
    "Prepare a semantic kernel instance first, loading also the AI service settings defined in the [Setup notebook](00-getting-started.ipynb):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1da651d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "!python -m pip install semantic-kernel==0.3.15.dev0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd150646",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import sys\n",
    "import semantic_kernel as sk\n",
    "from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion\n",
    "\n",
    "kernel = sk.Kernel()\n",
    "\n",
    "\n",
    "useAzureOpenAI = False\n",
    "\n",
    "# Configure AI service used by the kernel\n",
    "if useAzureOpenAI:\n",
    "    deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
    "    kernel.add_chat_service(\"chat_completion\", AzureChatCompletion(deployment, endpoint, api_key))\n",
    "else:\n",
    "    api_key, org_id = sk.openai_settings_from_dot_env()\n",
    "    kernel.add_chat_service(\"chat-gpt\", OpenAIChatCompletion(\"gpt-3.5-turbo\", api_key, org_id))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "186767f8",
   "metadata": {},
   "source": [
    "Let's create a **native** function that gives us a random number between 3 and a user input as the upper limit. We'll use this number to create 3-x paragraphs of text when passed to a semantic function."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "589733c5",
   "metadata": {},
   "source": [
    "First, let's create our native function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ae29c207",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "from semantic_kernel.skill_definition import sk_function\n",
    "\n",
    "class GenerateNumberSkill:\n",
    "    \"\"\"\n",
    "    Description: Generate a number between 3-x.\n",
    "    \"\"\"\n",
    "\n",
    "    @sk_function(\n",
    "        description=\"Generate a random number between 3-x\",\n",
    "        name=\"GenerateNumberThreeOrHigher\"\n",
    "    )\n",
    "    def generate_number_three_or_higher(self, input: str) -> str:\n",
    "        \"\"\"\n",
    "        Generate a number between 3-<input>\n",
    "        Example:\n",
    "            \"8\" => rand(3,8)\n",
    "        Args:\n",
    "            input -- The upper limit for the random number generation\n",
    "        Returns:\n",
    "            int value\n",
    "        \"\"\"\n",
    "        try:\n",
    "            return str(random.randint(3, int(input))) \n",
    "        except ValueError as e:\n",
    "            print(f\"Invalid input {input}\")\n",
    "            raise e"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "f26b90c4",
   "metadata": {},
   "source": [
    "Next, let's create a semantic function that accepts a number as `{{$input}}` and generates that number of paragraphs about two Corgis on an adventure. `$input` is a default variable semantic functions can use. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7890943f",
   "metadata": {},
   "outputs": [],
   "source": [
    "sk_prompt = \"\"\"\n",
    "Write a short story about two Corgis on an adventure.\n",
    "The story must be:\n",
    "- G rated\n",
    "- Have a positive message\n",
    "- No sexism, racism or other bias/bigotry\n",
    "- Be exactly {{$input}} paragraphs long\n",
    "\"\"\"\n",
    "\n",
    "corgi_story = kernel.create_semantic_function(prompt_template=sk_prompt,\n",
    "                                              function_name=\"CorgiStory\",\n",
    "                                              skill_name=\"CorgiSkill\",\n",
    "                                              description=\"Write a short story about two Corgis on an adventure\",\n",
    "                                              max_tokens=500,\n",
    "                                              temperature=0.5,\n",
    "                                              top_p=0.5)\n",
    "\n",
    "generate_number_skill = kernel.import_skill(GenerateNumberSkill())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2471c2ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run the number generator\n",
    "generate_number_three_or_higher = generate_number_skill[\"GenerateNumberThreeOrHigher\"]\n",
    "number_result = generate_number_three_or_higher(6)\n",
    "print(number_result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f043a299",
   "metadata": {},
   "outputs": [],
   "source": [
    "story = await corgi_story.invoke_async(input=number_result.result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59a60e2a",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Generating a corgi story exactly {} paragraphs long: \".format(number_result.result))\n",
    "print(\"=====================================================\")\n",
    "print(story)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "8ef29d16",
   "metadata": {},
   "source": [
    "## Context Variables\n",
    "\n",
    "That works! But let's expand on our example to make it more generic. \n",
    "\n",
    "For the native function, we'll introduce the lower limit variable. This means that a user will input two numbers and the number generator function will pick a number between the first and second input.\n",
    "\n",
    "We'll make use of the `semantic_kernel.ContextVariables` class to do hold these variables."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa5087bd",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import sys\n",
    "import semantic_kernel as sk\n",
    "from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion\n",
    "\n",
    "kernel = sk.Kernel()\n",
    "\n",
    "\n",
    "useAzureOpenAI = False\n",
    "\n",
    "# Configure AI service used by the kernel\n",
    "if useAzureOpenAI:\n",
    "    deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
    "    kernel.add_chat_service(\"chat_completion\", AzureChatCompletion(deployment, endpoint, api_key))\n",
    "else:\n",
    "    api_key, org_id = sk.openai_settings_from_dot_env()\n",
    "    kernel.add_chat_service(\"chat-gpt\", OpenAIChatCompletion(\"gpt-3.5-turbo\", api_key, org_id))\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "091f45e4",
   "metadata": {},
   "source": [
    "Let's start with the native function. Notice that we're also adding `@sk_function_context_parameter` decorators to the function here to provide context about what variables need to be provided to the function, and any defaults for those inputs. Using the `@sk_function_context_parameter` decorator provides the name, description and default values for a function's inputs to the [planner.](./05-using-the-planner.ipynb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4ea462c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter\n",
    "from semantic_kernel import SKContext\n",
    "\n",
    "class GenerateNumberSkill:\n",
    "    \"\"\"\n",
    "    Description: Generate a number between a min and a max.\n",
    "    \"\"\"\n",
    "\n",
    "    @sk_function(\n",
    "        description=\"Generate a random number between min and max\",\n",
    "        name=\"GenerateNumber\"\n",
    "    )\n",
    "    @sk_function_context_parameter(name=\"min\", description=\"Minimum number of paragraphs.\")\n",
    "    @sk_function_context_parameter(name=\"max\", description=\"Maximum number of paragraphs.\", default_value=10)\n",
    "    def generate_number(self, context: SKContext) -> str:\n",
    "        \"\"\"\n",
    "        Generate a number between min-max\n",
    "        Example:\n",
    "            min=\"4\" max=\"10\" => rand(4,8)\n",
    "        Args:\n",
    "            min -- The lower limit for the random number generation\n",
    "            max -- The upper limit for the random number generation\n",
    "        Returns:\n",
    "            int value\n",
    "        \"\"\"\n",
    "        try:\n",
    "            return str(random.randint(int(context[\"min\"]), int(context[\"max\"]))) \n",
    "        except ValueError as e:\n",
    "            print(f\"Invalid input {context['min']} {context['max']}\")\n",
    "            raise e"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48bcdf9e",
   "metadata": {},
   "outputs": [],
   "source": [
    "generate_number_skill = kernel.import_skill(GenerateNumberSkill())\n",
    "generate_number = generate_number_skill[\"GenerateNumber\"]"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "6ad068d6",
   "metadata": {},
   "source": [
    "Now let's also allow the semantic function to take in additional arguments. In this case, we're going to allow the our CorgiStory function to be written in a specified language. We'll need to provide a `paragraph_count` and a `language`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b8286fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "sk_prompt = \"\"\"\n",
    "Write a short story about two Corgis on an adventure.\n",
    "The story must be:\n",
    "- G rated\n",
    "- Have a positive message\n",
    "- No sexism, racism or other bias/bigotry\n",
    "- Be exactly {{$paragraph_count}} paragraphs long\n",
    "- Be written in this language: {{$language}}\n",
    "\"\"\"\n",
    "\n",
    "corgi_story = kernel.create_semantic_function(prompt_template=sk_prompt,\n",
    "                                              function_name=\"CorgiStory\",\n",
    "                                              skill_name=\"CorgiSkill\",\n",
    "                                              description=\"Write a short story about two Corgis on an adventure\",\n",
    "                                              max_tokens=500,\n",
    "                                              temperature=0.5,\n",
    "                                              top_p=0.5)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "fdce1872",
   "metadata": {},
   "source": [
    "Now we can call this using our `invoke` function by passing in our `context_variables` in the `variables` parameter. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "97d8d3c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "context_variables = sk.ContextVariables(variables={\n",
    "    \"min\": 1,\n",
    "    \"max\": 5,\n",
    "    \"language\": \"Spanish\"\n",
    "})"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "c8778bad",
   "metadata": {},
   "source": [
    "Let's add a paragraph count to our context variables "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28820d9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "context_variables['paragraph_count'] = generate_number.invoke(variables=context_variables).result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dbe07c4d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Pass the output to the semantic story function\n",
    "story = await corgi_story.invoke_async(variables=context_variables)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6732a30b",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "print(\"Generating a corgi story exactly {} paragraphs long in {} language: \".format(context_variables[\"paragraph_count\"],\n",
    "                                                                                    context_variables[\"language\"]))\n",
    "print(\"=====================================================\")\n",
    "print(story)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "fb786c54",
   "metadata": {},
   "source": [
    "## Calling Native Functions within a Semantic Function\n",
    "\n",
    "One neat thing about the Semantic Kernel is that you can also call native functions from within Semantic Functions!\n",
    "\n",
    "We will make our CorgiStory semantic function call a native function `GenerateNames` which will return names for our Corgi characters.\n",
    "\n",
    "We do this using the syntax `{{skill_name.function_name}}`. You can read more about our prompte templating syntax [here](../../../docs/PROMPT_TEMPLATE_LANGUAGE.md). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d84c7d84",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "from semantic_kernel.skill_definition import sk_function\n",
    "\n",
    "\n",
    "class GenerateNamesSkill:\n",
    "    \"\"\"\n",
    "    Description: Generate character names.\n",
    "    \"\"\"\n",
    "\n",
    "    # The default function name will be the name of the function itself, however you can override this\n",
    "    # by setting the name=<name override> in the @sk_function decorator. In this case, we're using\n",
    "    # the same name as the function name for simplicity.\n",
    "    @sk_function(\n",
    "        description=\"Generate character names\",\n",
    "        name=\"generate_names\"\n",
    "    )\n",
    "    def generate_names(self) -> str:\n",
    "        \"\"\"\n",
    "        Generate two names.\n",
    "        Returns:\n",
    "            str\n",
    "        \"\"\"\n",
    "        names = {\n",
    "            \"Hoagie\",\n",
    "            \"Hamilton\",\n",
    "            \"Bacon\",\n",
    "            \"Pizza\",\n",
    "            \"Boots\",\n",
    "            \"Shorts\",\n",
    "            \"Tuna\"\n",
    "        }\n",
    "        first_name = random.choice(list(names))\n",
    "        names.remove(first_name)\n",
    "        second_name = random.choice(list(names))\n",
    "        return f\"{first_name}, {second_name}\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ab7d65f",
   "metadata": {},
   "outputs": [],
   "source": [
    "generate_names_skill = kernel.import_skill(GenerateNamesSkill(), skill_name=\"GenerateNames\")\n",
    "generate_names = generate_names_skill[\"generate_names\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "94decd3e",
   "metadata": {},
   "outputs": [],
   "source": [
    "sk_prompt = \"\"\"\n",
    "Write a short story about two Corgis on an adventure.\n",
    "The story must be:\n",
    "- G rated\n",
    "- Have a positive message\n",
    "- No sexism, racism or other bias/bigotry\n",
    "- Be exactly {{$paragraph_count}} paragraphs long\n",
    "- Be written in this language: {{$language}}\n",
    "- The two names of the corgis are {{GenerateNames.generate_names}}\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73aca517",
   "metadata": {},
   "outputs": [],
   "source": [
    "corgi_story = kernel.create_semantic_function(prompt_template=sk_prompt,\n",
    "                                              function_name=\"CorgiStory\",\n",
    "                                              skill_name=\"CorgiSkill\",\n",
    "                                              description=\"Write a short story about two Corgis on an adventure\",\n",
    "                                              max_tokens=500,\n",
    "                                              temperature=0.5,\n",
    "                                              top_p=0.5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56e6cf0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "context_variables = sk.ContextVariables(variables={\n",
    "    \"min\": 1,\n",
    "    \"max\": 5,\n",
    "    \"language\": \"Spanish\"\n",
    "})\n",
    "context_variables['paragraph_count'] = generate_number.invoke(variables=context_variables).result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e980348",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Pass the output to the semantic story function\n",
    "story = await corgi_story.invoke_async(variables=context_variables)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4ade048",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Generating a corgi story exactly {} paragraphs long in {} language: \".format(context_variables[\"paragraph_count\"],\n",
    "                                                                                    context_variables[\"language\"]))\n",
    "print(\"=====================================================\")\n",
    "print(story)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "42f0c472",
   "metadata": {},
   "source": [
    "### Recap\n",
    "\n",
    "A quick review of what we've learned here:\n",
    "\n",
    "- We've learned how to create native and semantic functions and register them to the kernel\n",
    "- We've seen how we can use context variables to pass in more custom variables into our prompt\n",
    "- We've seen how we can call native functions within semantic function prompts.  \n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
